﻿using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Transactions;
using Security;
using Storage;

namespace Groupware
{
    /// <summary>
    /// This class provides methods to ask, accept, deny Friendship requests.
    /// A FriendshipRequest is sent from the user (referenced as Sender) whose ID is AskingUserID property
    /// to the user (referenced as Recipient) whose ID is RecipientID property, when Sender asks to Recipient
    /// to let him add him to his own buddy list.
    /// </summary>
    public class FriendshipRequest
    {

        private int ID;
        private int askingUserID;
        private int recipientID;

        /// <summary>
        /// The ID of the user who's requesting to the one who's receiving this request to be friends
        /// </summary>
        public int AskingUserID { get { return askingUserID; } }

        /// <summary>
        /// The ID of the user who's receiving this request
        /// </summary>
        public int RecipientID { get { return recipientID; } }

        /// <summary>
        /// Send a FriendshipRequest from the user askingUser to the user whose id is recipientID
        /// </summary>
        /// <param name="askingUser">The actually logged-in user, asking for friendship</param>
        /// <param name="recipientID">The id of the user this request is addressed to</param>
        public bool AskForFriendship(TripLoaUser askingUser, int recipientID)
        {
            using (TransactionScope scope = new TransactionScope())
            {             
                if (askingUser.UserID == recipientID)
                {
                    scope.Complete();
                    return false;
                }

                if (CheckUserNonexistanceAndFriendshipExistance(askingUser, recipientID))
                {
                    scope.Complete();
                    return false;
                }
                //Recipient and askingUser aren't already friends and
                //askingUser still exists
                //Check against the existance of previous requests
                Storage.EntityModels.Notifications previousRequest = null;
                try
                {
                    previousRequest =
                    (from Storage.EntityModels.Notifications n in StorageManager.Instance.Entities.Notifications
                     where n.Message.Type == Notification.FRIEND_REQUEST && n.User.Id == askingUser.UserID && n.Receiver.Id == recipientID
                     select n).ToList().First();
                }
                catch (ArgumentNullException) { }
                catch (InvalidOperationException) { }
                if (previousRequest != null)
                {
                    scope.Complete();
                    return false;
                }
                //Check against the existance of symmetrical request
                Storage.EntityModels.Notifications symmetricalRequest = null;
                try
                {
                    symmetricalRequest =
                    (from Storage.EntityModels.Notifications n in StorageManager.Instance.Entities.Notifications
                     where n.Message.Type == Notification.FRIEND_REQUEST && n.Receiver.Id == askingUser.UserID && n.User.Id == recipientID
                     select n).ToList().First();
                }
                catch (ArgumentNullException) { }
                catch (InvalidOperationException) { }
                Storage.EntityModels.TripLoaDataContext context = null;
                if (symmetricalRequest != null)
                {//A symmetrical request exists, then the recipient user
                    //already asked to be a friend of askingUser
                    askingUser.AddFriend(recipientID);
                    //Turn the symmetrical request, coming from recipient,
                    //into a friendship request acceptance notification addressed to askingUser
                    symmetricalRequest.Message.Date = DateTime.Now;
                    symmetricalRequest.IsRead = false;
                    symmetricalRequest.Message.Type = Notification.FRIEND_REQUEST_ACCEPTED;
                    //Create the friendship request acceptance notification
                    //addressed to recipient
                    context = this.Create(Notification.FRIEND_REQUEST_ACCEPTED,askingUser.UserID,recipientID);
                }
                else
                {//A symmetrical request doesn't exist, then the recipient user
                    //didn't previusly asked to be a friend of askingUser
                    //Create the friendship request addressed to recipient
                    context = this.Create(Notification.FRIEND_REQUEST,askingUser.UserID,recipientID);
                }
                //Save to DB
                context.SubmitChanges();
                scope.Complete();
                return true;
            }
        }

        /// <summary>
        /// Allows the users whose ids are AskingUserID and RecipientID properties to be friends
        /// </summary>
        /// <param name="user">The currently logged-in user who's accepting this request</param>
        public void AcceptFriendship(TripLoaUser user)
        {
            if (user.GetProfile().Id != this.recipientID) return;
            //The user who's requesting this operation is the recipient of this friendship request
            using (TransactionScope scope = new TransactionScope())
            {
                if (CheckUserNonexistanceAndFriendshipExistance(user, askingUserID))
                {
                    scope.Complete();
                    return;
                }
                //The user who's requesting this operation
                //and askingUser aren't already friends, and askingUser still exists
                Storage.EntityModels.Notifications request = null;
                try
                {
                    request =
                    (from Storage.EntityModels.Notifications n in StorageManager.Instance.Entities.Notifications
                     where n.Id == this.ID
                     select n).ToList().First();
                }
                catch (ArgumentNullException) { scope.Complete(); return; }
                catch (InvalidOperationException) { scope.Complete(); return; }
                //It's now clear to add the new friend
                user.AddFriend(askingUserID);
                //Turns this FriendshipRequest into a FriendshipNotification addressed to
                //this request sender (askingUser).
                request.Message.Type = Notification.FRIEND_REQUEST_ACCEPTED;
                request.IsRead = false;
                request.UserId = user.GetProfile().Id;
                request.ReceiverUserId = askingUserID;
                request.Message.Date = DateTime.Now;
                Storage.StorageManager.Instance.Entities.SubmitChanges();
                scope.Complete();
            }
        }

        /// <summary>
        /// Refuses the incoming request from the user whose id is AskingUserID property to be friend with
        /// the user whose id is RecipientID property
        /// </summary>
        /// <param name="user">The currently logged-in user who's refusing this request</param>
        public void DenyFriendship(TripLoaUser user)
        {
            if (user.UserID != this.recipientID) return;
            //The user who's requesting this operation is the recipient of this friendship request
            using (TransactionScope scope = new TransactionScope())
            {
                Storage.EntityModels.Notifications request = null;
                try
                {
                    request =
                    (from Storage.EntityModels.Notifications n in StorageManager.Instance.Entities.Notifications
                     where n.Id == this.ID
                     select n).ToList().First();
                }
                catch (ArgumentNullException) { }
                catch (InvalidOperationException) { }
                if (request != null)
                {
                    Storage.StorageManager.Instance.Entities.Notifications.DeleteOnSubmit(request);
                    Storage.StorageManager.Instance.Entities.Messages.DeleteOnSubmit(request.Message);
                    Storage.StorageManager.Instance.Entities.SubmitChanges();
                }
                scope.Complete();
            }
        }

        /// <summary>
        /// Checks if the user whose id is userID exists and if the user usr and those whose
        /// id is userID are friends already.
        /// </summary>
        /// <param name="usr">The logged-in user who's requesting the operation</param>
        /// <param name="userID">The id of the user that has to be checked</param>
        /// <returns>
        /// TRUE - If the user whose id is userID exists AND there is no friendship between the users
        /// FALSE - If the user whose id is userID doesn't exist OR the users are friends already.
        /// </returns>
        private bool CheckUserNonexistanceAndFriendshipExistance(TripLoaUser usr, int userID)
        {
            bool result = false;
            try
            {
                if (usr.GetProfile().IsFriendOf(userID))
                    result = true;
            }
            catch (Storage.Exceptions.UserNotFoundException) { result = true; }

            string userName = usr.GetProfile(userID).Username;
            Storage.EntityModels.User recipient = StorageManager.Instance.GetUserByUserName(userName);

            if (!recipient.BookedIn) return true;

            return result;
        }

        /// <summary>
        /// Creates and inserts for submit in the returned DataContext a new Notification and the
        /// associated new Message.
        /// </summary>
        /// <param name="type">The notification type</param>
        /// <returns>The DataContext the InsertOnSubmit calls are performed upon</returns>
        private Storage.EntityModels.TripLoaDataContext Create(int type, int askerID, int recipientID)
        {
            Storage.EntityModels.TripLoaDataContext context = null;
            Storage.EntityModels.Message acceptanceMessage =
                new Storage.EntityModels.Message()
                {
                    Title = "",
                    Text = "",
                    Priority = true,
                    Date = DateTime.Now,
                    Type = (byte)type
                };
            context = Storage.StorageManager.Instance.Entities;
            context.Messages.InsertOnSubmit(acceptanceMessage);
            //context.SubmitChanges();
            Storage.EntityModels.Notifications acceptanceNotification =
                new Storage.EntityModels.Notifications()
                {
                    Message = acceptanceMessage,
                    IsRead = false,
                    UserId = askerID,
                    ReceiverUserId = recipientID
                };
            //Prepare for saving to DB
            context.Notifications.InsertOnSubmit(acceptanceNotification);
            return context;
        }

        /// <summary>
        /// Creates an empty FriendshipRequest instance which can be sent
        /// calling the instance method AskForFriendship. After such a call, the instance
        /// is still empty.
        /// </summary>
        public FriendshipRequest() { }

        internal FriendshipRequest(int ID, int askingUserID, int recipientID)
        {
            this.ID = ID;
            this.askingUserID = askingUserID;
            this.recipientID = recipientID;
        }

    }

    /// <summary>
    /// This class represents a friendship notification.
    /// Such a kind of notification notifies to the user whose ID is RecipientID property,
    /// that he added a new friend, whose ID is NewFriendID property, to his buddy list,
    /// and that such a friend added the user to his own buddy list too.
    /// </summary>
    public class FriendshipNotification
    {

        private int ID;
        private int recipientID;
        private int newFriendID;

        /// <summary>
        /// The ID of the user this notification has to be showed to
        /// </summary>
        public int RecipientID { get { return recipientID; } }

        /// <summary>
        /// The ID of the user that has been added to this notification recipient's buddy list
        /// </summary>
        public int NewFriendID { get { return newFriendID; } }

        /// <summary>
        /// Deletes this Friendship request from the DB.
        /// If this method isn't called, this FriendshipNotification will be retrieved again
        /// the next time GroupWare.NotificationManager.getFriendshipNotificationsByUserID is called.
        /// </summary>
        /// <param name="user">The currently logged-in user who received this notification</param>
        public void Delete(TripLoaUser user)
        {
            if (user.UserID != this.recipientID) return;
            //The user who's requesting this operation is the recipient of this notification
            using (TransactionScope scope = new TransactionScope())
            {
                Storage.EntityModels.Notifications notification = null;
                try
                {
                    notification =
                        (from Storage.EntityModels.Notifications n in StorageManager.Instance.Entities.Notifications
                         where n.Id == this.ID
                         select n).ToList().First();
                }
                catch (ArgumentNullException) { }
                catch (InvalidOperationException) { }
                if (notification != null)
                {
                    Storage.StorageManager.Instance.Entities.Notifications.DeleteOnSubmit(notification);
                    Storage.StorageManager.Instance.Entities.Messages.DeleteOnSubmit(notification.Message);
                    Storage.StorageManager.Instance.Entities.SubmitChanges();
                }
                scope.Complete();
            }
        }

        internal FriendshipNotification(int ID, int recipientID, int newFriendID)
        {
            this.ID = ID;
            this.recipientID = recipientID;
            this.newFriendID = newFriendID;
        }

    }

}
